/**
 * A Sudoku board
 * @author Theresa Wellington
 * @date April 2013
 *
 */
public class Board {
	
	private int boardSize;
	private Cell[][] cells;
	
	/**
	 * Constructs a new board of a specified size, given boardInput
	 * @param boardInput the cell values
	 * @param boardSize the size of the board
	 */
	public Board(int[][] boardInput, int boardSize){
		this.boardSize = boardSize;
		this.cells = new Cell[boardSize][boardSize];
		//construct initial board
		for(int i=0; i<boardSize; i++){
			for(int j=0; j<boardSize; j++){
				cells[i][j] = new Cell(boardInput[i][j], boardSize);
			}
		}
		//place cells that are solved
		for(int i=0; i<boardSize; i++){
			for(int j=0; j<boardSize; j++){
				if(cells[i][j].isSolved()){
					placeCell(cells[i][j], i, j);
				}
			}
		}
	}
	
	public int getBoardSize(){
		return boardSize;
	}
	
	public Cell getCell(int row, int col){
		return cells[row][col];
	}
	
	/**
	 * 
	 * @return true if every cell is solved
	 */
	public boolean isSolved(){
		for(int i=0; i<boardSize; i++){
			for(int j=0; j<boardSize; j++){
				if(!cells[i][j].isSolved()){
					return false;
				}
			}
		}
		return true;
	}
	
	private void placeCell(Cell cell, int row, int column){
		int value=cell.getValue();
		placeCell(cell, row, column, value);
	}
	
	/**
	 * Sets the cell to solved and eliminates the value from the row, column, and box the cell lives in
	 * @param cell the cell that is solved
	 * @param row the row the cell belongs in
	 * @param column the column the cell belongs in
	 * @param value the value of the cell
	 */
	public void placeCell(Cell cell, int row, int column, int value){
		cell.setSolved(value);
		//for the row and column cell is in
		for(int k=0; k<boardSize; k++){
			if(!cells[row][k].isSolved()){
				cells[row][k].removePossibleValue(value);
			}
			if(!cells[k][column].isSolved()){
					cells[k][column].removePossibleValue(value);
			}
		}
		
		//for box the cell is in
		// want row mod boardSize = 0 to next row mod boardSize = 0
		// and want column mod boardSize = 0 to next column modBoardSize = 0
		int rowStart =  row - (row % (int) (Math.sqrt(boardSize)));
		int colStart = column - (column % (int) (Math.sqrt(boardSize)));
		int rowEnd = rowStart + (int) (Math.sqrt(boardSize)) - 1;
		int colEnd = colStart + (int) (Math.sqrt(boardSize)) - 1;
		//example row = 5, boardSize = 9
		// startCol = 5 - 5%3 = 5 - 2 = 3       endCol = 3+3-1 = 5
		for(int k=rowStart; k<=rowEnd; k++){
			for(int h=colStart; h<=colEnd; h++){
				if(!cells[k][h].isSolved()){
					cells[k][h].removePossibleValue(value);
				}
			}

		}
		
		
	}
	
	/**
	 * 
	 * @return a string representing the solved puzzle
	 */
	public String printBoard(){
		String returnString = "";
		for(int i=0; i<boardSize; i++){
			for(int j=0; j<boardSize; j++){
				returnString += cells[i][j].getValue();
				if((j+1)%Math.sqrt((double) boardSize)== 0){
					returnString+="|";
				}
				else{
					returnString += " ";
				}
			}
			returnString += "\n";
			if((i+1)%Math.sqrt((double) boardSize)== 0){
				returnString+="\n";
			}
		}
		
		return returnString;
	}
	
	/**
	 * 
	 * @return a string representing the possibilities for each cell in the board
	 */
	public String printBoardPossibles(){
		String returnString = "";
		for(int i=0; i<boardSize; i++){
			for(int j=0; j<boardSize; j++){
				returnString += cells[i][j].printPossibles(boardSize);
				if((j+1)%Math.sqrt((double) boardSize)== 0){
					returnString+="   ";
				}
			}
			returnString += "\n";
			if((i+1)%Math.sqrt((double) boardSize)== 0){
				returnString+="\n";
			}
		}
		
		return returnString;
	}
	

	/**
	 * Removes a value from all rows in a column, except the skip rows
	 * @param removeValue the value to remove
	 * @param removeCol the column to remove values from
	 * @param skipRow the row to skip
	 * @param skipRow2 the second row to skip
	 */
	public void removeColPossibilities(int removeValue, int removeCol, int skipRow, int skipRow2){
		for(int row=0; row<boardSize; row++){
			if(row!=skipRow && row!=skipRow2){
				//if the cell is not solved and not one of the two in the pairElim, remove possibleValue
				if(!cells[row][removeCol].isSolved()){
					cells[row][removeCol].removePossibleValue(removeValue);
				}
			}
		}
	}

	/**
	 * Removes a value from all columns in a row, except the skip columns
	 * @param removeValue the value to remove
	 * @param removeRow the row to remove values from
	 * @param skipCol the column to skip
	 * @param skipCol2 the second column to skip
	 */
	public void removeRowPossibilities(int removeValue, int removeRow, int skipCol, int skipCol2){
		for(int col=0; col<boardSize; col++){
			if(col!=skipCol && col!=skipCol2){
				//if the cell is not solved and not one of the two in the pairElim, remove possibleValue
				if(!cells[removeRow][col].isSolved()){
					cells[removeRow][col].removePossibleValue(removeValue);
				}
			}
		}
	}
	
	/**
	 * Removes possibilities from a box except in the two cells in the specified row-col and row2-col2
	 * @param removeValue the value to remove
	 * @param row the row of the first cell to skip
	 * @param row2 the row of the second cell to skip
	 * @param col the column of the first cell to skip
	 * @param col2 the column of the second cell to skip
	 * @param rowStart the start row for the box
	 * @param rowEnd the end row for the box
	 * @param colStart the start column for the box
	 * @param colEnd the end column for the box
	 * TODO: in a refactor, move logic for determine box rowStart, rowEnd, colStart, colEnd to here (or move all to SudokuSolver.java - logic shouldn't live in both places)
	 */
	public void removeBoxPossibilities(int removeValue, int row, int row2, int col, int col2, int rowStart, int rowEnd, int colStart, int colEnd){
		for(int curRow = rowStart; curRow<=rowEnd; curRow++){
			for(int curCol=colStart; curCol<=colEnd; curCol++){
				if(!(curRow==row && curCol==col)){
					if(!(curRow==row2 && curCol==col2)){
						if(!cells[curRow][curCol].isSolved()){
							cells[curRow][curCol].removePossibleValue(removeValue);
						}
					}
				}
			}
		}
	}
	
	
	/**
	 * Remove possibilites from rows in box besides matching row
	 * @param matchingBox the box to remove values from
	 * @param matchingRow the row to skip
	 * @param value the value to remove
	 */
	public void removeBoxRowPossibilities(int matchingBox, int matchingRow, int value){
		//determine start and end of rows and columns for the box
		int rowStart =  matchingRow - (matchingRow % (int) (Math.sqrt(boardSize)));
		int colStart = matchingBox * (int) (Math.sqrt(boardSize));;
		int rowEnd = rowStart + (int) (Math.sqrt(boardSize)) - 1;
		int colEnd = colStart + (int) (Math.sqrt(boardSize)) - 1;
		//remove value
		for(int row = rowStart; row<=rowEnd; row++){
			if(row!=matchingRow){
				for(int col=colStart; col<=colEnd; col++){
					if(!cells[row][col].isSolved()){
						cells[row][col].removePossibleValue(value);
					}
				}
			}
		}
	}
	
	//
	/**
	 * remove possibilities from columns in box besides matchingCol
	 * @param matchingBox the box to remove values from
	 * @param matchingCol the row to skip
	 * @param value the value to remove
	 */
	public void removeBoxColPossibilities(int matchingBox, int matchingCol, int value){
		int colStart =  matchingCol - (matchingCol % (int) (Math.sqrt(boardSize)));
		int rowStart = matchingBox * (int) (Math.sqrt(boardSize));;
		int colEnd = rowStart + (int) (Math.sqrt(boardSize)) - 1;
		int rowEnd = colStart + (int) (Math.sqrt(boardSize)) - 1;
		for(int col = colStart; col<=colEnd; col++){
			if(col!=matchingCol){
				for(int row=rowStart; row<=rowEnd; row++){
					if(!cells[row][col].isSolved()){
						cells[row][col].removePossibleValue(value);
					}
				}
			}
		}
	}
}
